// ThreadingUnitTest.cpp : Defines the entry point for the console application.
//

#include "windows.h"

#include<stdio.h>
#include <process.h> 


class Barrier {

	volatile long numThreads;

	volatile long numThreadsEntered_Barrier;
	volatile long numThreadsExited_Barrier;
	HANDLE entranceSemaphore;
	HANDLE exitSemaphore;




public:
	Barrier(int numThreadsToSync) :
		numThreads(numThreadsToSync),
		numThreadsEntered_Barrier(0),
		numThreadsExited_Barrier(0)
	{

		entranceSemaphore = CreateSemaphore(NULL,0,4096,NULL);
		exitSemaphore = CreateSemaphore(NULL,0,4096,NULL);



	}

	~Barrier() 
	{ }

	void wait(){

		LONG prev;
		

		// Wait until all threads enter the Barrier
		if (InterlockedIncrement(&numThreadsEntered_Barrier) < numThreads)
			WaitForSingleObject(entranceSemaphore,INFINITE);
		else {
			numThreadsExited_Barrier = 0;
			 //single_thread_callback();
			ReleaseSemaphore(entranceSemaphore, numThreads-1, &prev);
		}

		//all_threads_callback();



		// Wait until all threads exit the Barrier
		if (InterlockedIncrement(&numThreadsExited_Barrier) < numThreads)
			WaitForSingleObject(exitSemaphore,INFINITE);
		else {
			numThreadsEntered_Barrier = 0;
			ReleaseSemaphore(exitSemaphore, numThreads-1, &prev);
		}



	}

	void openBarrier()
	{ 
		LONG prev1, prev2;
			
		ReleaseSemaphore(entranceSemaphore, 4096, &prev1);  //thsi must be made max, as if sem drops to 0(as wait release threads), remaining threads remain halted
		ReleaseSemaphore(exitSemaphore, 4096, &prev2);

		numThreadsEntered_Barrier = 0;
		numThreadsExited_Barrier = 0;	
	}

	void setMaxCount(long numThreadsToSync)
	{ numThreads = numThreadsToSync; }

	long  getMaxCount()
	{ return numThreads; };
};


/**
 * The Bump_BulletBase to create and manage a BulletBase
 */
class Base
{
//#pragma message("Base included")

	int baseID;

	volatile unsigned int count;	 

	//thread specific stuff;
	HANDLE hThread;
	unsigned threadID;
	bool keepRunningThread;

	CRITICAL_SECTION critsec;


public:

	static Barrier *simStepBarrier;
	bool threadRunning;

	Base(int id)
	{
				
		baseID = id;
		count = 0;

		InitializeCriticalSection(&critsec);
	}

	~Base()
	{
		
		printf_s("Base::ended %d\n", baseID);
		CloseHandle(hThread);
		DeleteCriticalSection(&critsec);
	};

	void MemberFunc(UINT i) // A member function
	{


		printf_s("Base: %d, count=%d \n", baseID, count);
		count++;			

	}

	int init()
	{
		keepRunningThread = true;
		hThread = (HANDLE)_beginthreadex(NULL , 0, &runBaseThread, this, 0, &threadID);

		return 0;
	}

	int exit()
	{
		keepRunningThread = false;
		//while(threadRunning)
		//	Sleep(10);

		printf_s("Base: %d, count=%d \n", baseID, count);
		return 0;
	}
		
	static unsigned __stdcall runBaseThread(void *param)
	{
		Base* This = (Base*)param;
		
		printf_s("Base::MemberFunc : %d STARTING\n", This->baseID);
		

		This->threadRunning = true;

		while(This->keepRunningThread){
			This->MemberFunc(0); // call a member function
			simStepBarrier->wait();
		}		

		printf_s("Base::MemberFunc : %d ENDING\n", This->baseID);
		
		Sleep(100);
		
		This->threadRunning = false;

		_endthreadex(0);
		return 0;
	}

	
};

Barrier * Base::simStepBarrier = NULL;

int main(int argc, char* argv[])
{
	Barrier simStepBarrier(4);
	Base *ptrBase[3];
	Base::simStepBarrier = &simStepBarrier;
	
	//Start base threads
	ptrBase[0] = new Base(1);
	ptrBase[1] = new Base(2);
	ptrBase[2] = new Base(3);

	ptrBase[0]->init();
	ptrBase[1]->init();
	ptrBase[2]->init();



	for(int i=0; i<10; i++)
		simStepBarrier.wait();
	
	

	//New base		
	ptrBase[0]->exit();	
	ptrBase[1]->exit();
	ptrBase[2]->exit();	
	
	//Release all threads for exit rush if this thread is also synced
	simStepBarrier.openBarrier();

	for(int i=0; i<3; i++)
		while(ptrBase[i]->threadRunning);
	
	
	

	//Safe to delete now
	delete ptrBase[0];
	delete ptrBase[1];
	delete ptrBase[2];
	
	
	
	return 0;
}

